#include "RayTracer.h"
#include "tribox.h"
#include "material.h"
#include "numeric.h"
#include <maya/MItDag.h>
#include <maya/MDagPath.h>

#include <maya/MStatus.h>
#include <maya/MFnDagNode.h>
#include <maya/MFnCamera.h>
#include <maya/MFnLight.h>
#include <maya/MColor.h>
#include <maya/MFnNurbsSurface.h>
#include <maya/MIOStream.h>
#include <maya/MPoint.h>
#include <maya/MVector.h>
#include <maya/MString.h>
#include <maya/MFnTransform.h>
#include <maya/MTransformationMatrix.h>
#include <maya/MMatrix.h>

#include <maya/MItDependencyNodes.h>
#include <maya/MLibrary.h>
#include <maya/MFileIO.h>

#include <maya/MFnMesh.h>
#include <maya/MFnMeshData.h>
#include <maya/MPointArray.h>
#include <maya/MItMeshVertex.h>
#include <maya/MItMeshPolygon.h>
//#include <maya/MFnPlugin.h>
#include <maya/MImage.h>
#include <maya/MFloatMatrix.h>
#include <maya/MCommonSystemUtils.h>

#include<maya/MFnAmbientLight.h>
#include<maya/MFnDirectionalLight.h>
#include<maya/MFnPointLight.h>
#include <maya/MPlug.h>
#include <maya/MPlugArray.h>
#include <maya/MMaterial.h>
#include <maya/MFnLambertShader.h>
#include <maya/MFnPhongShader.h>
#include <maya/MFnBlinnShader.h>
#include <maya/MTimer.h>
#include <vector>

const BBox BBoxSentinel = {std::numeric_limits<float>::max(), std::numeric_limits<float>::lowest(),
  std::numeric_limits<float>::max(), std::numeric_limits<float>::lowest(),
  std::numeric_limits<float>::max(), std::numeric_limits<float>::lowest()};


void* RayTracer::creator() 
{ 
  return new RayTracer; 
}



//---------------------------------------------------------------------------
MStatus RayTracer::doIt(const MArgList& argList) 
{
  // create the output directory if it doesn't already exist
  MString img_dir = buildOutputDirectoryName();
  MCommonSystemUtils::makeDirectory(img_dir);
  int charlen = img_dir.length();
  const char* tmp = img_dir.asChar(charlen);

	std::fstream file;

	std::string str(tmp);
	str += "performance_report.txt";
	file.open(str,ios::out);

  int ret_width;
  int ret_height;
  int ret_voxelFactor;
  int ret_superSample;
  int ret_verbose;
  parseArgs(argList, ret_width, ret_height, ret_voxelFactor, ret_superSample, ret_verbose);
  int arg_width       = (ret_width != -1) ? ret_width : 1920;
  int arg_height      = (ret_height != -1) ? ret_height : 1080;
  int arg_voxelFactor = (ret_voxelFactor != -1) ? ret_voxelFactor : 1;
  int arg_superSample = (ret_superSample != -1) ? ret_superSample : 1;
  int arg_verbose     = (ret_verbose != -1) ? ret_verbose : 1;

  cout << endl << endl << endl << endl;
  cout << "==============================" << endl;
  cout << "raytrace" << endl;
  cout << "   width: " << arg_width << endl;
  cout << "   height: " << arg_height << endl;
  cout << "   voxel factor: " << arg_voxelFactor << endl;
  cout << "   super sampling: " << arg_superSample << endl;
  cout << "   verbose: " << (arg_verbose?"on":"off") << endl;
  SYSTEMTIME sys_time;
  ::GetLocalTime(&sys_time);
  cout << "Called on " << sys_time.wYear << "/" << sys_time.wMonth << "/" << sys_time.wDay <<  " at " <<
    sys_time.wHour << ":" << sys_time.wMinute << ":" << sys_time.wSecond << "." << sys_time.wMilliseconds << endl;

  // init timer
  double init_time = 0.0;
  MTimer timer;
  timer.clear();
  timer.beginTimer();
  file << "Preprocessing begun.." << endl;
  file << "Triangulating the meshes in the scene.." << endl;

  // triangulate meshes
  triangulate_meshes();

  timer.endTimer();
  file << "Elapsed time: " << timer.elapsedTime() << endl;
  init_time += timer.elapsedTime();
  file << "Reading meshes information.." << endl;
  timer.clear();
  timer.beginTimer();

  // create a vector of structures with MeshInfo
  std::vector<MeshInfo> mesh_info_vec;
  create_meshinfo_list(mesh_info_vec);

  // create a vector of structures with triangle info
  std::vector<std::vector<triangle>> triangles_vector;
	int total_number_of_triangles = 0;  
  create_triangles_list(triangles_vector, total_number_of_triangles);

  // get a bounding box enclosing all meshes in the scene
  float scale_factor = 1.1f;
  BBox final_bbox;
  get_scene_bounding_box(final_bbox, scale_factor);

  timer.endTimer();
  init_time += timer.elapsedTime();
  file << "Done reading meshes information!" << endl;
  file << "Elapsed time: " << timer.elapsedTime() << endl;
  file << "Voxel space initialization begun.." << endl;
  timer.clear();
  timer.beginTimer();

  // create the voxel space  
  VoxelSpace vxspc(final_bbox, arg_voxelFactor);
  vxspc.init(triangles_vector);

  timer.endTimer();
  init_time += timer.elapsedTime();
  file << "Voxel space initialization ended!" << endl;
  file << "Elapsed time: " << timer.elapsedTime() << endl;
  timer.clear();
  timer.endTimer();
  file << "Reading Camera information.." << endl;
  timer.beginTimer();

  // get render plane info
  RenderPlaneInfo rp_info;
  get_renderplane_info(arg_width, arg_height, rp_info);

  timer.endTimer();
  init_time += timer.elapsedTime();
  file << "Done reading camera information!" << endl;
  file << "Elapsed time: " << timer.elapsedTime() << endl;
  file << "Reading lights information.." << endl;
  timer.clear();
  timer.beginTimer();

  // get all lighting information. For each light:
  std::vector<LightInfo> light_info_list;
  create_lightinfo_list(light_info_list);

  timer.endTimer();
  init_time += timer.elapsedTime();
  file << "Reading lights information ended!" << endl;
  file << "Elapsed time: " << timer.elapsedTime() << endl;
  file << "Preprocessing ended!" << endl;
  file << "Total time for preprecessing " << init_time << endl;
  file << "Ray Casting started" << endl;
  timer.clear();
  timer.beginTimer();

  // create the image
  MStatus stat_img;
  MImage  img;
  stat_img                    = img.create(rp_info.ImgResX, rp_info.ImgResY);
  int depth                   = img.depth();
  unsigned char* pixel_array  = img.pixels();
  memset(pixel_array, 0, rp_info.ImgResX*rp_info.ImgResY*depth);
  MFloatPoint raySource;
  MVector rayDir;
  float maxParam              = 1000.0f;
  MFloatPoint hitPoint;
  MFloatPoint minHitPoint;
  float2 uv1,uv2,uv3,minuv;
  //float hitRayParam;
  float minHitRayParam;
  float minHitRayParamCheck = FLT_MAX;
  //int hitFace;
	int minHitFace = 0;
  //int hitTriangle;
  //int minHitTriangle;
  //float hitBary1, hitBary2;
	float minHitBary1 = 1.0, minHitBary2 = 1.0;
  MStatus stat_ci;
  MObject* hitMesh = NULL;
  int meshIdx = 0;
  int minMeshIdx = 0;

  int first_line = 0;

  std::vector<int> voxels_to_draw;
  std::vector<Idx3> voxels_to_draw_idx3;
  MPoint origin_to_draw;
  MPoint dest_to_draw;
  MPoint pt_in_draw;
  MPoint pt_out_draw;

  MVector ray_scaled;
  MPoint pt1_scaled;
  MPoint pt2_scaled;

	MTimer scene_timer;
	scene_timer.beginTimer(); 
	
  MTimer pixel_timer;
  std::vector<float> deviation_calc;
  MTimer group_timer;
  for(int y=0; y<rp_info.ImgResY; ++y)
  {
    if((y%10 == 0) || ((y>=470) && (y<=480))){
      group_timer.endTimer();
      cout << "Processed lines " << first_line << " to " << y << " in " << group_timer.elapsedTime() << " s." << endl;
      cout.flush();
      first_line = y;
      group_timer.clear();
      group_timer.beginTimer();
    }
    

    for(int x=0; x<rp_info.ImgResX; ++x)
    {
      pixel_timer.clear();
      pixel_timer.beginTimer();
      MColor avg_color;
      int color_sample_count = 0;
      float px_off = 1.0f/(arg_superSample+1); // pixel offset
      for(int sx=0; sx<arg_superSample; ++sx)      
      {
        for(int sy=0; sy<arg_superSample; ++sy)
        {
          MVector pixelPos3d = rp_info.LeftBottom + 
            (double(y) + (sy+1)*px_off)*(rp_info.PixelHeight*rp_info.upDirection) + 
            (double(x) + (sx+1)*px_off)*rp_info.PixelWidth*rp_info.rightDirection;

          raySource.setCast(pixelPos3d);
          if(rp_info.isOrtho)
          {
            rayDir = rp_info.viewDirection;
            rayDir.normalize();
          }
          else
          {
            MVector sampleRay = pixelPos3d - rp_info.eyePoint;
            rayDir = MFloatVector(sampleRay);
            rayDir.normalize();
          }

          // loop through all meshes, finding the closest intersection point
          minHitRayParam = std::numeric_limits<float>::max();
          minHitRayParamCheck = FLT_MAX;
          MItDependencyNodes mesh_it(MFn::kMesh);
          mesh_it.reset(MFn::kMesh);
          meshIdx = 0;
          MFnMesh minMesh;
          bool bIntersectedAtAll = false;
          bool bIntersectedAtAllCheck = false;

          float minHitRayParamOur, BaryOur1, BaryOur2, BaryOur3;
          int minMeshOur, minHitTriangleOur; 
          MFloatPoint minHitPointOur;

          bool bIntersectedAtAllOur = vxspc.ourColsestIntersection(rayDir, raySource, minHitRayParamOur, minMeshOur, minHitTriangleOur, minHitPointOur, BaryOur1, BaryOur2, BaryOur3);
          if(bIntersectedAtAllOur)
          {
            MPoint vtx1 = mesh_info_vec.at(minMeshOur).m_faceGeoms.at(minHitTriangleOur).m_pts.at(0);
            MPoint vtx2 = mesh_info_vec.at(minMeshOur).m_faceGeoms.at(minHitTriangleOur).m_pts.at(1);
            MPoint vtx3 = mesh_info_vec.at(minMeshOur).m_faceGeoms.at(minHitTriangleOur).m_pts.at(2);
            const MString str = (mesh_info_vec.at(minMeshOur).m_material.set);
            mesh_it.reset(MFn::kMesh);
            for(int ind = 0 ; ind < minMeshOur ; ind++)
            { mesh_it.next();}
            MDagPath curr_mesh_dagpath = MDagPath::getAPathTo(mesh_it.item());
            MFnMesh curr_mesh(curr_mesh_dagpath);
            curr_mesh.getUVAtPoint(vtx1,uv1,MSpace::kWorld,&str);
            curr_mesh.getUVAtPoint(vtx2,uv2,MSpace::kWorld,&str);
            curr_mesh.getUVAtPoint(vtx3,uv3,MSpace::kWorld,&str);
            minuv[0] = BaryOur1*uv1[0] + BaryOur2*uv2[0] + (1.0f-BaryOur1-BaryOur2)*uv3[0];
            minuv[1] = BaryOur1*uv1[1] + BaryOur2*uv2[1] + (1.0f-BaryOur1-BaryOur2)*uv3[1];
          }
          if(bIntersectedAtAllOur){
            if(minHitFace != minHitTriangleOur || minMeshOur != minMeshIdx){
              bool stop = true;
            }
          }



          if(bIntersectedAtAllOur)
          {
            static int debugging = 0;
            if(debugging)
            {
              pixel_array[y*rp_info.ImgResX*depth + x*depth + 0] = 255;
              pixel_array[y*rp_info.ImgResX*depth + x*depth + 1] = 255;
              pixel_array[y*rp_info.ImgResX*depth + x*depth + 2] = 255;
              pixel_array[y*rp_info.ImgResX*depth + x*depth + 3] = 255;
            }
            else        
            {
              // overall mesh info struct
              const MeshInfo& curr_mesh_info = mesh_info_vec[minMeshOur];

              // materials
              const Material& curr_mat = curr_mesh_info.m_material;


              const MeshInfo::FaceGeom& curr_face_geom = curr_mesh_info.m_faceGeoms[minHitTriangleOur];
              MFloatPoint triV1 = curr_face_geom.m_pts[0];
              MFloatPoint triV2 = curr_face_geom.m_pts[1];
              MFloatPoint triV3 = curr_face_geom.m_pts[2];
              MFloatVector normV1 = curr_face_geom.m_normals[0];
              MFloatVector normV2 = curr_face_geom.m_normals[1];
              MFloatVector normV3 = curr_face_geom.m_normals[2];

              MFloatVector normWeighted = (BaryOur1*normV1 + BaryOur2*normV2 + (1.0f - BaryOur1 - BaryOur2)*normV3);
              MFloatPoint ptWeighted = (triV1*BaryOur1 + triV2*BaryOur2 + triV3*(1.0f - BaryOur1 - BaryOur2));

              // gouraud (per-vertex)
              //MColor colV1 = calcColorAtPt(triV1, normV1, curr_mat, light_info_list);
              //MColor colV2 = calcColorAtPt(triV2, normV2, curr_mat, light_info_list);
              //MColor colV3 = calcColorAtPt(triV3, normV3, curr_mat, light_info_list);
              //MColor pixel_color = colV1*minHitBary1 + colV2*minHitBary2 + colV3*(1-minHitBary1-minHitBary2);

              // phong (per-pixel)
              MColor pixel_color = calcColorAtPt(vxspc, minuv[0], minuv[1], ptWeighted, normWeighted,rp_info.eyePoint, curr_mat, light_info_list,
                minMeshOur, minHitTriangleOur, BaryOur1, BaryOur2);

              // add it to the sum
              avg_color += pixel_color;
              ++color_sample_count;

            }
          }
          else
          {
            // add it to the sum
            avg_color += MColor(0,0,0,1);
            ++color_sample_count;

            //pixel_array[y*ImgResX*depth + x*depth + 0] = 0;
            //pixel_array[y*ImgResX*depth + x*depth + 1] = 0;
            //pixel_array[y*ImgResX*depth + x*depth + 2] = 0;
            //pixel_array[y*ImgResX*depth + x*depth + 3] = 255;
          }

        }
      }

      // we have the sum; now divide

      avg_color /= float(color_sample_count);
      pixel_array[y*rp_info.ImgResX*depth + x*depth + 0] = RoundToInt(avg_color.r*255);
      pixel_array[y*rp_info.ImgResX*depth + x*depth + 1] = RoundToInt(avg_color.g*255);
      pixel_array[y*rp_info.ImgResX*depth + x*depth + 2] = RoundToInt(avg_color.b*255);
      pixel_array[y*rp_info.ImgResX*depth + x*depth + 3] = 255;
      pixel_timer.endTimer();
      deviation_calc.push_back((float)pixel_timer.elapsedTime());

    } // for x->ImgResX

  } // for y->ImgResY

  scene_timer.endTimer();
  double time_elapsed = scene_timer.elapsedTime();
  cout << "Raytracing completed in " << time_elapsed << " s." << endl;

  Visualizer viz;
  if(false)
  {
    //voxels_to_draw_idx3;
    //voxels_to_draw;
    //pt_in_draw;
    //pt_out_draw;

    MBoundingBox bboxsc(MPoint(0,0,0), MPoint(arg_voxelFactor,arg_voxelFactor,arg_voxelFactor));
    viz.drawSpace(arg_voxelFactor, bboxsc);
    viz.deselectAll();
    viz.drawPoint(pt1_scaled.x, pt1_scaled.y, pt1_scaled.z, "pt1_scaled");
    viz.drawPoint(pt2_scaled.x, pt2_scaled.y, pt2_scaled.z, "pt2_scaled");
    viz.drawRay(pt1_scaled, ray_scaled);
    viz.deselectAll();

    // visualize anything we're supposed to
    //for(int pt=0; pt<voxels_to_draw.size(); ++pt)
    //{
    //  Idx3 ijk_ = voxels_to_draw_idx3[pt];
    //  Idx3 ijk = vxspc.idx_3d_from_idx(voxels_to_draw[pt]);
    //  int i = ijk.x; //points_to_draw[pt].x;
    //  int j = ijk.y; //points_to_draw[pt].y;
    //  int k = ijk.y; //points_to_draw[pt].z;
    //  const BBox& box = vxspc.bounding_box();
    //  MBoundingBox bbox(MPoint(box.xmin, box.ymin, box.zmin), MPoint(box.xmax, box.ymax, box.zmax));
    //  viz.drawVoxel(arg_voxelFactor, bbox, i, j, k);
    //}
    //viz.deselectAll();

    // draw the rays
    //viz.drawRay(origin_to_draw, dest_to_draw);
    viz.drawPoint(pt_in_draw.x, pt_in_draw.y, pt_in_draw.z, "pt_in");
    viz.deselectAll();
    viz.drawPoint(pt_out_draw.x, pt_out_draw.y, pt_out_draw.z, "pt_out");
    viz.deselectAll();
  }  

  // create the output directory

   timer.endTimer();
  init_time += timer.elapsedTime();
  file << "RayCating ended!!" << endl;
  file << "Render time: " << timer.elapsedTime() << endl;
  file << "total_time: " << init_time << endl;
  file << "time_per_pixel: " << timer.elapsedTime() / (rp_info.ImgResX*rp_info.ImgResY) << endl;
  float sum = 0;
  float average = float(timer.elapsedTime()) / (rp_info.ImgResX*rp_info.ImgResY);
  for(long long int i = 0 ; i < deviation_calc.size() ; i++)
  {
	  sum += ((deviation_calc.at(i)-average)*(deviation_calc.at(i)-average));
  }
  sum /= deviation_calc.size();
  sum = sqrt(sum);
  file << "TimePerPixelDeviation: " << sum << endl;
  file << "The number of polygons in the scene is: " << total_number_of_triangles << endl;
	file << "polygonsPerRay: " << vxspc.total_polygons_for_all_rays / (rp_info.ImgResX*rp_info.ImgResY) << endl; //multiply by supersampling factor
	file << "voxelsPerRay:" << double(vxspc.total_voxels_for_all_rays) / double(rp_info.ImgResX*rp_info.ImgResY) << endl; //multiply by supersampling factor
	file << "intersectionTest: " << vxspc.total_polygons_for_all_rays << endl;
	file << "Intersections: " << double(vxspc.found_intersections_with_polygons)/double(vxspc.total_polygons_for_all_rays) << endl;

  file.close();

  // finally, write the file
  MString output_filename = img_dir + MString("scene.tif");
  stat_img = img.writeToFile(output_filename,"tif");
  if(stat_img) {
    MString msg = MString("Successfully wrote scene file ")+output_filename;
    MGlobal::displayInfo(msg);
    cout << msg.asChar() << endl;
  } else {
    MGlobal::displayInfo(MString("Error writing scene file ")+output_filename);
  }

  // release the image
  img.release();
  for(int m=0; m<mesh_info_vec.size(); ++m)
  {
    if(mesh_info_vec[m].m_material.m_isTextured){
      mesh_info_vec[m].m_material.release_image();
    }
  }
  return MStatus::kSuccess;
}

//---------------------------------------------------------------------------
MString RayTracer::buildOutputDirectoryName()
{
  MString user_dir = MCommonSystemUtils::getEnv(MString("MAYA_APP_DIR"));
  MString img_dir = user_dir;
  MString last_char = img_dir.substring(img_dir.length()-1, img_dir.length()-1);
  if((MString("/") != last_char) && (MString("\\") != last_char)){
    img_dir += MString("/");
  }
  img_dir += MString("img/");
  return img_dir;
}

//---------------------------------------------------------------------------
int RayTracer::intArg(const MArgList& args, unsigned int &indx, int & res)
{
  if (indx < args.length())
  {
    MStatus stat;
    indx++;
    res = args.asInt( indx, &stat );
    if (stat == MS::kSuccess){
      return 1;
    }
  }
  return 0;
}

//---------------------------------------------------------------------------
MStatus RayTracer::parseArgs(const MArgList& args,
                             int& width,
                             int& height,
                             int& voxelFactor,
                             int& superSample,
                             int& verbose)
{
	MStatus     	stat;
	MString     	arg;
	const MString	widthFlag				("-w");
	const MString	heightFlag			("-h");
	const MString	voxelFactorFlag	("-n");
	const MString	superSampleFlag	("-s");
	const MString	verboseFlag			("-v");

  width       = -1;
  height      = -1;
  voxelFactor = -1;
  superSample = -1;
  verbose     = -1;

	// Parse the arguments.
	for ( unsigned int i = 0; i < args.length(); i++ ) {
		arg = args.asString( i, &stat );
		if (!stat){
			continue;
    }

		if (arg == widthFlag)
			intArg(args, i, width);
		else if (arg == heightFlag)
			intArg(args, i, height);
		else if (arg == voxelFactorFlag)
			intArg(args, i, voxelFactor);
		else if (arg == superSampleFlag)
			intArg(args, i, superSample);
		else if (arg == verboseFlag)
			verbose = 1;
		else {
			displayError(arg + ": unknown argument");
			//return MS::kFailure;
		}
	}
	return stat;
}

//---------------------------------------------------------------------------
bool RayTracer::getBoundingBox(const MDagPath& dagPath, bool quiet, BBox& bbox_)
{
  BBox bound_box = BBoxSentinel;

  MItMeshVertex it (dagPath);
  int count_vertices = 0;
  it.reset();
  while(!it.isDone())
  {
    MObject item = it.currentItem();
    MPoint pos = it.position(MSpace::kWorld);
    if(pos.x < bound_box.xmin) {
      bound_box.xmin = float(pos.x);
    }
    if(pos.x > bound_box.xmax) {
      bound_box.xmax = float(pos.x);
    }
    if(pos.y < bound_box.ymin) {
      bound_box.ymin = float(pos.y);
    }
    if(pos.y > bound_box.ymax) {
      bound_box.ymax = float(pos.y);
    }
    if(pos.z < bound_box.zmin) {
      bound_box.zmin = float(pos.z);
    }
    if(pos.z > bound_box.zmax) {
      bound_box.zmax = float(pos.z); 
    }
    it.next();
  }

  bbox_ = bound_box;
  return true;
}

//---------------------------------------------------------------------------
bool RayTracer::getBoundingBox(const std::vector<BBox>& bboxes, BBox& bbox_)
{
  if(bboxes.empty())
  { return false; }

  // loop through all bounding boxes
  BBox bound_box = bboxes[0];
  for(int i=1; i<bboxes.size(); ++i)
  {
    // compare each limit
    const BBox curr_bbox = bboxes[i];
    if(curr_bbox.xmin < bound_box.xmin) {
      bound_box.xmin = curr_bbox.xmin;
    }
    if(curr_bbox.xmax > bound_box.xmax) {
      bound_box.xmax = curr_bbox.xmax;
    }
    if(curr_bbox.ymin < bound_box.ymin) {
      bound_box.ymin = curr_bbox.ymin;
    }
    if(curr_bbox.ymax > bound_box.ymax) {
      bound_box.ymax = curr_bbox.ymax;
    }
    if(curr_bbox.zmin < bound_box.zmin) {
      bound_box.zmin = curr_bbox.zmin;
    }
    if(curr_bbox.zmax > bound_box.zmax) {
      bound_box.zmax = curr_bbox.zmax; 
    }
  }

  // send it out
  bbox_ = bound_box;
  return true;
}

//---------------------------------------------------------------------------
MColor RayTracer::calcColorAtPt(VoxelSpace vxspc, float uv1, float uv2, const MFloatPoint& vertPt, const MFloatVector& vertNorm, const MFloatPoint& eyePoint, 
                                const Material& material, const std::vector<LightInfo>& light_info_list,
								int& mesh_idx, int& face_idx, float& bary1, float& bary2)
{
  MColor col(1.0f, 0.0f, 1.0f);
  if(material.m_isTextured){
    material.getBilinearFilteredPixelColor(uv1,uv2,col);
  }

  MColor pixel_color(0, 0, 0);
  for(int l=0; l<light_info_list.size(); ++l)
  {
    const LightInfo& li = light_info_list[l];
    MColor curr_component(0, 0, 0);
    switch (li.light_type)
    {
    case MFn::kAmbientLight:
      {
        // ka * ia
        curr_component = (material.m_ambient * li.color) * li.intensity;
		  //curr_component = (col * li.color) * li.intensity;
        break;
      }
    case MFn::kPointLight:
      {
        MFloatVector surf_light_vec = li.pos - vertPt;
        surf_light_vec.normalize();
				if(!lightSeesPoint(vxspc, li.pos, -surf_light_vec, mesh_idx, face_idx, bary1, bary2)){
          continue;
        }

        float norm_vec_dot = surf_light_vec * vertNorm;
        norm_vec_dot = clampbot(norm_vec_dot, 0.0f);
       // curr_component = (material.m_diffuse * norm_vec_dot * li.color);
        if(material.m_isTextured){
          curr_component = (col * norm_vec_dot * li.color);
        } else {
          curr_component = (material.m_diffuse * norm_vec_dot * li.color);
        }

        // add the specular component
        MFloatVector surf_light_ref = 2*norm_vec_dot*vertNorm - surf_light_vec;
        surf_light_ref.normalize();
        MFloatVector surf_eye_vec = eyePoint - vertPt;
        surf_eye_vec.normalize();
        float norm_ref_dot = surf_light_ref*surf_eye_vec;
        norm_ref_dot = clampbot(norm_ref_dot, 0.0f);
        curr_component += (material.m_specular * pow(norm_ref_dot, material.m_specular.a) * li.color);

        // multiply by the intensity
        curr_component *= li.intensity;
        break;
      }
    case MFn::kDirectionalLight:
      {
				MFloatVector vv(-1*li.dir.x,-1*li.dir.y,-li.dir.z);
				if(!lightSeesPoint(vxspc, vertPt, li.dir, mesh_idx, face_idx, bary1, bary2, true)){
          continue;
        }
        // dir is the direction of the light. do the dot with -dir
        // the dot product comes positive when incident ray and norm are in opposite dirs
        float norm_vec_dot = (-li.dir) * vertNorm;
        norm_vec_dot = clampbot(norm_vec_dot, 0.0f);
        if(material.m_isTextured){
          curr_component = (col * norm_vec_dot * li.color);
        } else {
          curr_component = (material.m_diffuse * norm_vec_dot * li.color);
        }

        // add the specular component
        MFloatVector surf_light_ref = 2*norm_vec_dot*vertNorm + li.dir;
        surf_light_ref.normalize();
        MFloatVector surf_eye_vec = eyePoint - vertPt;
        surf_eye_vec.normalize();
        float norm_ref_dot = surf_light_ref*surf_eye_vec;
        norm_ref_dot = clampbot(norm_ref_dot, 0.0f);
        curr_component += (material.m_specular * pow(norm_ref_dot, material.m_specular.a) * li.color);

        // multiply by the intensity
        curr_component *= li.intensity;

        break;
      }
    }

    pixel_color += curr_component;
    pixel_color.r = clamptop(pixel_color.r, 1.0f);
    pixel_color.g = clamptop(pixel_color.g, 1.0f);
    pixel_color.b = clamptop(pixel_color.b, 1.0f);
  }

  return pixel_color;

}

//---------------------------------------------------------------------------
bool RayTracer::lightSeesPoint(VoxelSpace& vxspc, const MFloatPoint& light_pos, const MVector& light_vec, int mesh_idx, int face_idx, float bary1, float bary2, bool directional_light) const
{
	MFloatPoint hitPoint;
	//int hitFace;
  //int hitTriangle;
  int minMeshIdx=-1,minFaceIdx=-1;
	//float hitBary1;
  //float hitBary2;
  float hitBary3;
  //float hitRayParam;
  float minhitRayParam = FLT_MAX;
  float minBary1=-1.;
  float minBary2=-1.;

  bool intersects_any_mesh = false;
  MItDependencyNodes mesh_it(MFn::kMesh);
  int meshIdx = 0;

  bool bIntersectsOur = false;
  MFloatPoint pt;
  if(directional_light){
    pt.x = light_pos.x - vxspc.getDiagLength() * float(light_vec.x);
    pt.y = light_pos.y - vxspc.getDiagLength() * float(light_vec.y);
    pt.z = light_pos.z - vxspc.getDiagLength() * float(light_vec.z);
  }
  else {
    pt = light_pos;
  }
  bIntersectsOur = vxspc.ourColsestIntersection(light_vec, pt, minhitRayParam, minMeshIdx, minFaceIdx, hitPoint, minBary1, minBary2, hitBary3);
  if(bIntersectsOur)
  {
    intersects_any_mesh = true;
    if(minMeshIdx != mesh_idx && directional_light)
      return false;
  }

  //if(directional_light && !intersects_any_mesh)
  //	return true;
  if(minMeshIdx != mesh_idx)
  { return false; }
  
  if(minFaceIdx != face_idx)
  { return false; }
  
  if(abs(minBary1-bary1)>0.0001 || abs(minBary2-bary2)>0.0001)
  { return false; }
  
  return true;
}

//---------------------------------------------------------------------------
void RayTracer::triangulateMesh(const MFnMesh& mesh)
{ 
    MString cmd("polyTriangulate -ch 0 "); 
    cmd += mesh.name(); 
    MGlobal::executeCommand( cmd ); 
    cmd = "select -cl";
    MGlobal::executeCommand( cmd );
}

//---------------------------------------------------------------------------
void RayTracer::triangulate_meshes()
{
  // loop through all the meshes
  MItDependencyNodes mesh_it(MFn::kMesh);
  for(; !mesh_it.isDone(); mesh_it.next())
  {
    // get the object
    MObject obj = mesh_it.item();
    MFnDagNode objNode(obj);
    cout << "triangulate_meshes(): " << objNode.name().asChar() << ": " <<  obj.apiTypeStr();

    // attach the function set
    MDagPath aPath = MDagPath::getAPathTo(obj);
    MFnMesh mesh(aPath);
    int pvc = mesh.polygonVertexCount(0);
    cout << " " << pvc << " vertices per polygon." << endl;

    // triangulate    
    triangulateMesh(mesh);
  }
}

//---------------------------------------------------------------------------
void RayTracer::get_scene_bounding_box(BBox& scene_bb, float scale_factor)
{
  MItDependencyNodes mesh_it(MFn::kMesh);
  std::vector<BBox> bbox_vector;
  BBox curr_bbox;
	for( ; !mesh_it.isDone(); mesh_it.next())
  {
    // get the object and a dagpath to it
    MObject obj = mesh_it.item();
    MFnDagNode objNode(obj);
    cout << "get_scene_bounding_box(): Including bounding box for: " << objNode.name().asChar() << " | " <<  obj.apiTypeStr() << endl;
    MDagPath aPath = MDagPath::getAPathTo(obj);

    // get and store the bounding box
    getBoundingBox(aPath, true, curr_bbox);
    bbox_vector.push_back(curr_bbox);
  }

  // calculate scene bounding box
  BBox all_obj_bbox;
  getBoundingBox(bbox_vector, all_obj_bbox);

  // expand it by a small factor
  float ctrx, ctry, ctrz;
  all_obj_bbox.center(ctrx,ctry,ctrz);
  float ext_x, ext_y, ext_z;
  all_obj_bbox.extents_half(ext_x, ext_y, ext_z);
  
  scene_bb.xmin = ctrx - (scale_factor*ext_x);
  scene_bb.xmax = ctrx + (scale_factor*ext_x);
  scene_bb.ymin = ctry - (scale_factor*ext_y);
  scene_bb.ymax = ctry + (scale_factor*ext_y);
  scene_bb.zmin = ctrz - (scale_factor*ext_z);
  scene_bb.zmax = ctrz + (scale_factor*ext_z);

}

//---------------------------------------------------------------------------
void RayTracer::create_meshinfo_list(std::vector<MeshInfo>& mesh_info_vec)
{
  MItDependencyNodes mesh_it(MFn::kMesh);
	for( ; !mesh_it.isDone(); mesh_it.next())
  {
    MeshInfo curr_mesh_geom;

    // get the object/name
    MObject obj = mesh_it.item();
    MFnDagNode objNode(obj);
    cout << "create_meshinfo_list(): creating MeshInfo struct for: " << objNode.name().asChar() << " | " <<  obj.apiTypeStr() << endl;
    curr_mesh_geom.m_name = objNode.name();
    
    // attach the function set
    MDagPath aPath = MDagPath::getAPathTo(obj);
    MFnMesh mesh(aPath);

    // m_pts on the whole mesh
    MFloatPointArray vertexList;
    mesh.getPoints(vertexList, MSpace::kWorld);
    for(unsigned int v=0; v<vertexList.length(); ++v){
      curr_mesh_geom.m_pts.push_back(vertexList[v]);
    }

    // per-polygon stuff
    MItMeshPolygon face_it(aPath);

    for(; !face_it.isDone(); face_it.next())
    {
      MeshInfo::FaceGeom curr_face_geom;

      // index
      int poly_idx = face_it.index();
      curr_face_geom.m_index = poly_idx;

      MIntArray polyVertList;
      mesh.getPolygonVertices(poly_idx, polyVertList);
      for(unsigned int pv=0; pv<polyVertList.length(); ++pv)
      {
        // point index in m_ptIdx
        int curr_pt_idx = polyVertList[pv];
        curr_face_geom.m_ptIdx.push_back(curr_pt_idx);

        // actual point (vertex) in m_pts
        MFloatPoint curr_pt = curr_mesh_geom.m_pts[curr_pt_idx];
        curr_face_geom.m_pts.push_back(curr_pt);

        // and m_normals
        MVector dcurr_normal;
        mesh.getFaceVertexNormal(poly_idx, polyVertList[pv], dcurr_normal, MSpace::kWorld);
        MFloatVector curr_normal(dcurr_normal);
        curr_face_geom.m_normals.push_back(curr_normal);
      }

      curr_mesh_geom.m_faceGeoms.push_back(curr_face_geom);
    }

    // push object on and then load the material 
    // (since we changed MImage to a pointer this might not be crucial)
    mesh_info_vec.push_back(curr_mesh_geom);
    mesh_info_vec[mesh_info_vec.size()-1].m_material.load(mesh);
  }

}

//---------------------------------------------------------------------------
void RayTracer::create_triangles_list(std::vector<std::vector<triangle>>& triangles_vector, int& triangle_count)
{
  MItDependencyNodes mesh_it(MFn::kMesh);
  triangles_vector.clear();

	size_t total_number_of_triangles = 0;  
	for( ; !mesh_it.isDone(); mesh_it.next())
  {
    // get the object
    MObject obj = mesh_it.item();
    MFnDagNode objNode(obj);
    cout << "create_triangles_list(): " << objNode.name().asChar() << " | " <<  obj.apiTypeStr() << endl;
    
    // attach the function set
    MDagPath aPath = MDagPath::getAPathTo(obj);
    MFnMesh mesh(aPath);

    // m_pts on the whole mesh
    MFloatPointArray vertexList;
    mesh.getPoints(vertexList, MSpace::kWorld);

    // save the vertices, triangles and normals for each mesh
    MIntArray Trianglesm,CountTriangles;
    mesh.getPoints(vertexList, MSpace::kWorld);
    int l = vertexList.length();
    mesh.getTriangles(CountTriangles, Trianglesm);
    int hn = Trianglesm.length();

    int ind = 0;
    std::vector<triangle> triangles;
    for(unsigned int t = 0 ; t < CountTriangles.length() ; t++)
    {
      int co = CountTriangles[t];
      for(int j = 0 ; j < co ; j++)
      {
        triangle tri;
        tri.p1 = vertexList[Trianglesm[ind*3]];
        tri.p2 = vertexList[Trianglesm[ind*3+1]];
        tri.p3 = vertexList[Trianglesm[ind*3+2]];
        triangles.push_back(tri);
        ind++;
      }
    }
    total_number_of_triangles += triangles.size();
    triangles_vector.push_back(triangles);
  }

  triangle_count = (int)total_number_of_triangles;
}

//---------------------------------------------------------------------------
MStatus RayTracer::get_renderplane_info(int width, int height, RenderPlaneInfo& rp_info)
{

  // look at all cameras
  bool found_non_default_cam = false;
  int ImgResX = width;
  int ImgResY = height;
  MPoint LeftBottom;
  double PixelHeight;
  double PixelWidth;
  MFloatPoint eyePoint;
  MVector viewDirection;
  MVector upDirection;
  MVector rightDirection;
  bool isOrtho = false;

  double ImgPlaneH;
  double ImgPlaneW;
  double aspectRatio;

  double left;
  double right;
  double bottom;
  double top;
  double windowAspect = 1.0;
  
  MPoint imagePlaneCenter;

  MItDependencyNodes cam_it(MFn::kCamera);
  while(!cam_it.isDone())
  {
    MObject obj = cam_it.item();
    MDagPath aPath = MDagPath::getAPathTo(obj);
    MStatus status;
    MFnCamera camera (aPath, &status);
    if ( !status ) {
      status.perror("MFnCamera constructor");
      continue;
    }
    std::cout << "Found camera " << camera.name().asChar();

    // skip if it's one of the default cameras
    if((MString("perspShape") == camera.name()) ||
      (MString("topShape") == camera.name()) ||
      (MString("frontShape") == camera.name()) ||
      (MString("sideShape") == camera.name()))
    {
      std::cout << "[default camera; skipping...]" << std::endl;
      cam_it.next();
      continue;
    }

    std::cout << std::endl;
    found_non_default_cam = true;
    camera.getRenderingFrustum(windowAspect, left, right, bottom, top);
    MPoint dEyePoint = camera.eyePoint(MSpace::kWorld);
    eyePoint.setCast(dEyePoint);

    viewDirection =	camera.viewDirection(MSpace::kWorld); 
    viewDirection.normalize();
    upDirection = camera.upDirection(MSpace::kWorld);
    upDirection.normalize();
    rightDirection = camera.rightDirection(MSpace::kWorld);
    rightDirection.normalize();

    double focalLengthCM = camera.focalLength() / 10.0;
    // film aperture is in inches.
    double horizontalFilmApertureCM = camera.horizontalFilmAperture() * 2.54; 
    aspectRatio = camera.aspectRatio();

    ImgPlaneW = horizontalFilmApertureCM;
    ImgPlaneH = ImgPlaneW / aspectRatio;

    if(camera.isOrtho())
    {
      isOrtho = true;
      float orthographic_width = (float)camera.orthoWidth();
      aspectRatio = camera.aspectRatio();
      ImgPlaneW = orthographic_width;
      ImgPlaneH = ImgPlaneW / aspectRatio;
    }

    //viewAngle = 2*atan(horizontalFilmAperture/(2*focalLength));
    //H = 2*focalLength*tan(viewAngle/2);

    //W = H*aspectRatio;
    MVector tmp = viewDirection*focalLengthCM;
    MPoint p(eyePoint);
    p.x = p.x + tmp.x; p.y = p.y + tmp.y; p.z = p.z + tmp.z;
    imagePlaneCenter = p;

    LeftBottom = imagePlaneCenter - (ImgPlaneW/2)*rightDirection;
    LeftBottom = LeftBottom - (ImgPlaneH/2)*upDirection;
    PixelWidth = ImgPlaneW/ImgResX;
    PixelHeight = ImgPlaneH/ImgResY;

    break;
  }

  if(!found_non_default_cam)
  {
    std::cout << "Found no non-default cameras. Exiting..." << std::endl;
    return MStatus::kFailure;
  }

  rp_info.ImgResX = width;
  rp_info.ImgResY = height;
  rp_info.LeftBottom = LeftBottom;
  rp_info.PixelHeight = PixelHeight;;
  rp_info.PixelWidth = PixelWidth;
  rp_info.eyePoint = eyePoint;
  rp_info.viewDirection = viewDirection;
  rp_info.upDirection = upDirection;
  rp_info.rightDirection = rightDirection;
  rp_info.isOrtho = isOrtho;
  rp_info.ImgPlaneH = ImgPlaneH;
  rp_info.ImgPlaneW = ImgPlaneW;
  rp_info.aspectRatio = aspectRatio;


  std::cout << (isOrtho?"Orthographic":"Perspective") << " Cam Properties: " << std::endl;
  std::cout << "  {Left, Right, Bot, Top} = {" << left << ", " << right << ", " << 
    bottom << ", " << top << "}" << std::endl;


  return MStatus::kSuccess;
}

//---------------------------------------------------------------------------
void RayTracer::create_lightinfo_list(std::vector<LightInfo>& light_info_list)
{
  light_info_list.clear();
  MItDependencyNodes light_it(MFn::kLight);
  while(!light_it.isDone())
  {
    MDagPath light_path = MDagPath::getAPathTo(light_it.item());
    MStatus stat;
    MFnLight curr_light(light_path, &stat);
    if(!stat)  {
      std::cout << "Not a light." << std::endl;
      continue;
    }

    // is a light, continue.
    std::cout << "create_lightinfo_list(): Found light of type " << light_it.item().apiTypeStr() << " ";

    MFn::Type light_api_type = light_it.item().apiType();
    if((MFn::kAmbientLight != light_api_type) && 
      (MFn::kDirectionalLight != light_api_type) && 
      (MFn::kPointLight != light_api_type))
    { 
      std::cout << "[Not Ambient, Directional or Point, skipping...]" << std::endl;
      continue;
    }

    MColor light_color = curr_light.color();
    std::cout << "(R,G,B) = (" << light_color.r << ", " << light_color.g << ", "
      << light_color.b << ")" << std::endl;

    MMatrix light_mat = light_path.inclusiveMatrix(&stat);
    if(!stat)
    {
      std::cout << "Error getting inclusive matrix." << std::endl;
    }

    MPoint origin(0, 0, 0, 1);
    MVector xaxis(1, 0, 0);
    MVector yaxis(0, 1, 0);
    MVector zaxis(0, 0, 1);
    MVector origin_world = origin * light_mat;
    MVector xaxis_world = xaxis * light_mat;
    MVector yaxis_world = yaxis * light_mat;
    MVector zaxis_world = zaxis * light_mat; 

    // to find the direction the light is pointing.
    MVector negZaxis(0, 0, -1);
    MVector negZaxis_world = negZaxis * light_mat;

    MFnDagNode transform(light_path.transform());
    MMatrix transformMat = transform.transformationMatrix();
    MTransformationMatrix light_transform(transformMat);
    MFloatPoint  light_pos = light_transform.getTranslation(MSpace::kWorld);

    LightInfo curr_light_info;
    curr_light_info.light_path = light_path;
    curr_light_info.light_type = light_api_type;
    curr_light_info.pos = light_pos;
    curr_light_info.dir = MFloatVector(0, 0, 0);
    curr_light_info.color = curr_light.color();
    curr_light_info.intensity = curr_light.intensity();
    if(MFn::kDirectionalLight == light_api_type){
      curr_light_info.dir = negZaxis_world;
    }

    light_info_list.push_back(curr_light_info);
    light_it.next();
  }
}